/*
 * BLLib.Configuration.Option
 * Andy Tidball
 * 
 * Project: Black Lightning General Library
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using BLLib.Xml;

namespace BLLib.Configuration {
	/// <summary>
	/// Represents a single option with OK/Cancel functionality and different values for global, user, and profile.
	/// </summary>
	public class Option : ICloneable {
		/// <summary>
		/// The name of the XML tag that this class saves it data to.
		/// </summary>
		public const string XmlTagName = "option";

		/// <summary>
		/// The name of the XML attribute that stores the Option's name in its XML tag.
		/// </summary>
		public const string XmlNameAttribute  = "name";

		/// <summary>
		/// The name of the XML attribute that stores the Option's value in its XML tag.
		/// </summary>
		public const string XmlValueAttribute = "value";
/*
		/// <summary>
		/// Returns some of the bytes from a given array of bytes.
		/// </summary>
		/// <param name="Count">The number of bytes to return.</param>
		/// <param name="Bytes">An array of bytes.</param>
		/// <returns>An array of the given number of bytes taken from the passed in bytes.</returns>
		private static byte[] GetBytes(int Count, byte[] Bytes) {
			byte[] Output = new byte[Count];
			int c = Bytes.Length / Count;
			int i = 0;
			int o = 0;
			while (o < Count) {
				Output[o] = Bytes[i];
				o+=1;
				i+=c;
			}
			return Output;
		}
*/
		/// <summary>
		/// Turns a byte array into a string.
		/// </summary>
		/// <param name="Bytes">The bytes to turn into a string.</param>
		/// <returns>A string that retains unprintable characters.</returns>
		private static string BytesToString(byte[] Bytes) {
			StringBuilder Output = new StringBuilder();
			for (int i=0; i<Bytes.Length; ++i) {
				Output.Append((char)Bytes[i]);
			}
			return Output.ToString();
		}

		/// <summary>
		/// Turns a string into an array of bytes.
		/// </summary>
		/// <param name="String">The string to turn into an array of bytes.</param>
		/// <returns>An array of bytes that retains unprintable characters.</returns>
		private static byte[] StringToBytes(string String) {
			byte[] Output = new byte[String.Length];
			for (int i=0; i<String.Length; ++i) {
				Output[i] = (byte)String[i];
			}
			return Output;
		}

		/// <summary>
		/// Encrypts the given value.
		/// </summary>
		/// <param name="Value">The value to encrypt.</param>
		/// <param name="Key">The key to encrypt the value with.</param>
		/// <returns>The value after having been encrypted with the given key.</returns>
		private static string EncryptValue(string Value, string Key) {
			// if the key or value is null, then no encryption is necessary
			if (Key == null || Value == null || Value == string.Empty) {
				return Value;
			}

			// encrypt the values with an encrypter based on the hashes of the key
			RijndaelManaged RM = new RijndaelManaged();
			RM.BlockSize = 256;
			string EncryptedValue = CryptoValue(Value, RM.CreateEncryptor(new MD5CryptoServiceProvider().ComputeHash(StringToBytes(Key)), new SHA256Managed().ComputeHash(StringToBytes(Key))));
			RM.Clear();

			// return the encrypted value
			return EncryptedValue;
		}

		/// <summary>
		/// Decrypts the values of the Option and stores them over the previous values.
		/// </summary>
		/// <param name="Value">The value to decrypt.</param>
		/// <param name="Key">The key to decrypt the value with.</param>
		/// <returns>The value after having been decrypted with the given key.</returns>
		private static string DecryptValue(string Value, string Key) {
			// if the key or value is null, then no encryption is necessary
			if (Key == null || Value == null || Value == string.Empty) {
				return Value;
			}

			// encrypt the values with an encrypter based on the hashes of the key
			RijndaelManaged RM = new RijndaelManaged();
			RM.BlockSize = 256;
			string DecryptedValue = CryptoValue(Value, RM.CreateDecryptor(new MD5CryptoServiceProvider().ComputeHash(StringToBytes(Key)), new SHA256Managed().ComputeHash(StringToBytes(Key))));
			RM.Clear();

			// return the decrypted value
			return DecryptedValue;
		}

		/// <summary>
		/// Runs the Option's values through a cryptography stream based on the given transform.
		/// </summary>
		/// <param name="Value">The value to encrypt/decrypt using the specified cryptographic transform.</param>
		/// <param name="Transformer">The transform to use on the specified value.</param>
		private static string CryptoValue(string Value, ICryptoTransform Transformer) {
			// create a crypto stream
			MemoryStream MS = new MemoryStream();
			CryptoStream CS = new CryptoStream(MS, Transformer, CryptoStreamMode.Write);

			// crypto the value
			byte[] ByteValue = StringToBytes(Value);
			CS.Write(ByteValue, 0, ByteValue.Length);
			CS.FlushFinalBlock();
			string CryptoedValue = BytesToString(MS.ToArray());
			CS.Clear();
			CS.Close();
			MS.Close();

			// return the value
			return CryptoedValue;
		}

		/// <summary>
		/// Checks if one type derives from another, directly or indirectly.
		/// </summary>
		/// <param name="T">The type to check.  If T is null, the return value will always be true.</param>
		/// <param name="DerivesFrom">The type that T must derive from.</param>
		/// <returns>True if T derives from type DerivesFrom, false if not.</returns>
		private static bool DerivesFrom(Type T, Type DerivesFrom) {
			// a null value is fine
			if (T == null) {
				return true;
			}

			// loop through the type's base types
			while (T != null) {
				// if the base type is the type we want, then it's good
				if (T.BaseType == DerivesFrom) {
					return true;
				}

				// move to the next higher base type
				T = T.BaseType;
			}

			// no base types were the given type, return false
			return false;
		}

		// member data
		private bool _Initialized = false; // whether or not we've been initialized yet

		// member data - values
		private Dictionary<int, object> _TemporaryValues;  // the option's temporary values
		private Dictionary<int, object> _PermanentValues;  // the option's permanent values
		private int                     _LowestValueIndex; // the lowest value index in the option

		// member data - properties
		private Type   _ValueType    = typeof(string); // the type of the Option's values
		private string _Name         = string.Empty;   // the Option's name
		private object _DefaultValue = string.Empty;   // the Option's default value
		private string _Description  = null;           // the Option's description
		private string _Category     = null;           // the name of the Option's category
		private bool   _Hidden       = true;           // whether or not the Option is hidden from the user
		private bool   _ReadOnly     = true;           // whether or not the Option is read only
		private Type   _Editor       = null;           // the UI editor used to edit the Option's values
		private Type   _Converter    = null;           // the converter used to convert the Option's values to and from strings
		private string _EncryptKey   = null;           // the key to use to encrypt and decrypt the Option's value while stored on disk

		// member data - other
		private OptionSet _Parent = null; // the option set that this option belongs to

		/// <summary>
		/// Creates a new Option.
		/// </summary>
		/// <param name="Name">The new Option's name.</param>
		public Option(string Name) {
			// setup member data
			_LowestValueIndex = int.MaxValue;

			// store our name
			_Name = Name;

			// initialize our value containers
			_TemporaryValues = new Dictionary<int,object>();
			_PermanentValues = new Dictionary<int,object>();
		}

		/// <summary>
		/// Creates a new Option by loading it from an XML tag.
		/// </summary>
		/// <param name="Tag">The tag to load the Option from.</param>
		public Option(XmlTag Tag)
			: this(Tag, 0) {
		}

		/// <summary>
		/// Creates a new Option by loading it from an XML tag.
		/// </summary>
		/// <param name="Tag">The tag to load the Option from.</param>
		/// <param name="ValueIndex">The value index to store the loaded information in.</param>
		public Option(XmlTag Tag, int ValueIndex) {
			// setup member data
			_LowestValueIndex = int.MaxValue;

			// load from the tag
			Load(Tag, ValueIndex);
		}

		/// <summary>
		/// Initializes the Option with all the information needed for it to be used.
		/// </summary>
		/// <param name="Initializer">The set of initialization information for the Option.</param>
		public void Initialize(OptionInitializer Initializer) {
			Initialize(Initializer.ValueType, Initializer.DefaultValue, Initializer.Description, Initializer.Category, Initializer.Hidden, Initializer.ReadOnly, Initializer.EncryptKey, Initializer.Editor, Initializer.Converter);
			InitializeValues(Initializer.ValueIndices);
		}

		/// <summary>
		/// Initializes the object with everything except its name and value (value may have been loaded in earlier).
		/// </summary>
		/// <param name="ValueType">The Type of the Option's values.</param>
		/// <param name="DefaultValue">The Option's default value (must be of type ValueType).</param>
		/// <param name="Description">A description of the Option.  Can be null.</param>
		/// <param name="Category">The category the Option will appear in.  Can be null.</param>
		/// <param name="Hidden">Whether or not the Option is hidden.</param>
		/// <param name="ReadOnly">Whether or not the Option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the Option's values while stored on disk, or null to not store the values encrypted.  Only valid if ValueType == typeof(string).</param>
		/// <param name="Editor">A UITypeEditor that can be used to edit the Option's values, or null to use the default for ValueType.</param>
		/// <param name="Converter">A TypeConverter that can convert ValueType to and from strings, or null to use the default for ValueType.</param>
		private void Initialize(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor, Type Converter) {
			// make sure that the given default value is of type ValueType
			if (DefaultValue.GetType() != ValueType) {
				throw new ArgumentException("The given default value's type '" + DefaultValue.GetType().Name + "' does not match the given value type '" + ValueType.Name + "'.");
			}

			// make sure that the editor and converter are derived from the appropriate types
			if (!Option.DerivesFrom(Editor, typeof(UITypeEditor))) {
				throw new ArgumentException("The given editor type '" + Editor.Name + "' is not derived from UITypeEditor.");
			}
			if (!Option.DerivesFrom(Converter, typeof(TypeConverter))) {
				throw new ArgumentException("The given converter type '" + Converter.Name + "' is not derived from TypeConverter.");
			}

			// make sure that we can convert the current values (strings) to the new value type with the given converter or the default one
			TypeConverter C = (Converter == null ? TypeDescriptor.GetConverter(ValueType) : Converter.GetConstructor(System.Type.EmptyTypes).Invoke(null) as TypeConverter);
			if (!C.CanConvertFrom(typeof(string))) {
				throw new ArgumentException("Unable to convert the current values of type '" + typeof(string).Name + "' to the given type '" + ValueType.Name + "' using a converter of type '" + C.GetType().Name + "'.");
			}

			// store the member data
			_ValueType    = ValueType;
			_DefaultValue = DefaultValue;
			_Description  = Description;
			_Category     = Category;
			_Hidden       = Hidden;
			_ReadOnly     = ReadOnly;
			_Editor       = Editor;
			_Converter    = Converter;
			_EncryptKey   = EncryptKey;

			// run through each value that we have and convert it to the given type
			foreach (KeyValuePair<int, object> Pair in _PermanentValues) {
				this[Pair.Key] = C.ConvertFrom(DecryptValue(Pair.Value as string, _EncryptKey));
			}

			// apply the newly set values
			ApplyNewValues(false);

			// set our initialized flag
			_Initialized = true;
		}

		/// <summary>
		/// Initializes the values for a set of value indices.  The highest given index is set to the default value.  Lower indices are set to the value of the next higher index.
		/// </summary>
		/// <param name="ValueIndices">The indices of values to initialize.</param>
		private void InitializeValues(params int[] ValueIndices) {
			// make sure that we're initialize
			if (!Initialized) {
				throw new InvalidOperationException("Unable to initialize an Option's values before the Option itself has been initialized with Initialize().");
			}

			// sort the given indices
			List<int> Indices = new List<int>(ValueIndices);
			Indices.Sort();

			// run through the indices from greatest to least
			object PreviousValue = this.DefaultValue;
			for (int i=Indices.Count-1; i >= 0; --i) {
				// get the current value index
				int ValueIndex = Indices[i];

				// check if the option has a value for this index yet
				if (!HasValueAtIndex(ValueIndex)) {
					// if it doesn't, assign it the value from the last value index we set
					this[ValueIndex] = PreviousValue;
				}

				// store the value we set for use with later indices
				PreviousValue = GetTemporaryValue(ValueIndex);
			}

			// apply the new values
			ApplyNewValues();
		}

		/// <summary>
		/// Sets an Initialized Option back to its Uninitialized state, so only its Name and Value are known as if it were just Loaded.
		/// </summary>
		public void Uninitialize() {
			// create a converter before we change our value type
			TypeConverter C = CreateConverter();

			// reset all our member data
			_ValueType    = typeof(string);
			//_DefaultValue = string.Empty; // leave the default value so that if the Option is saved it can properly compare current values to it
			_Description  = null;
			_Category     = null;
			_Hidden       = true;
			_ReadOnly     = true;
			_Editor       = null;
			_Converter    = null;
			//_EncryptKey   = null; // leave the encryption key so that if the Option is saved it is properly encrypted

			// convert all our values back to strings
			_DefaultValue = C.ConvertTo(_DefaultValue, typeof(string));
			foreach (KeyValuePair<int, object> Pair in _PermanentValues) {
				this[Pair.Key] = C.ConvertTo(Pair.Value, typeof(string));
			}

			// apply the new values
			ApplyNewValues();

			// set our flag back to false
			_Initialized  = false;
		}

		/// <summary>
		/// Gets whether or not the Option has been Initialized.
		/// </summary>
		public bool Initialized {
			get {
				return _Initialized;
			}
		}

		/// <summary>
		/// Gets the permanent value at the given index or sets the temporary value at the given index.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to get or set.  If setting at a value index that doesn't exist, that index is created.</param>
		/// <returns>The permanent value at the given index, or null if the Option doesn't have a value at that index.</returns>
		public object this[int ValueIndex] {
			get {
				if (_PermanentValues.ContainsKey(ValueIndex)) {
					return _PermanentValues[ValueIndex];
				} else {
					return null;
				}
			}
			set {
				// if we have a value type, make sure that the given value is compatible
				if (ValueType != null && !ValueType.IsAssignableFrom(value.GetType())) {
					throw new ArgumentException("Given value '" + value.ToString() + "' is not compatible with the Option's value type of '" + ValueType.ToString() + "'.");
				}

				// make sure this index exists in our values
				if (!_TemporaryValues.ContainsKey(ValueIndex)) {
					_TemporaryValues.Add(ValueIndex, null);
					_PermanentValues.Add(ValueIndex, null);
					if (ValueIndex < _LowestValueIndex) {
						_LowestValueIndex = ValueIndex;
					}
				}

				// fire an event to see if we should store the new value
				OptionValueChangingEventArgs Args = new OptionValueChangingEventArgs(ValueIndex, _TemporaryValues[ValueIndex], _PermanentValues[ValueIndex]);
				OnTemporaryValueChanging(Args);
				if (!Args.Cancel) {
					_TemporaryValues[ValueIndex] = value;
					OnTemporaryValueChanged(Args);
				}
			}
		}

		/// <summary>
		/// Gets or sets the Option's value at the lowest known value index.
		/// </summary>
		public object Value {
			get {
				return this[_LowestValueIndex];
			}
			set {
				this[_LowestValueIndex] = value;
			}
		}

		/// <summary>
		/// Gets the lowest index in the Option that currently has a value.
		/// </summary>
		public int LowestValueIndex {
			get {
				return _LowestValueIndex;
			}
		}

		/// <summary>
		/// Gets the type of the Option's values.
		/// </summary>
		public Type ValueType {
			get {
				return _ValueType;
			}
		}

		/// <summary>
		/// Gets or sets the Option's name.
		/// </summary>
		public string Name {
			get {
				return _Name;
			}
		}

		/// <summary>
		/// Gets or sets the Option's description.
		/// </summary>
		public string Description {
			get {
				return _Description;
			}
		}

		/// <summary>
		/// Gets or sets the Option's default value.
		/// </summary>
		public object DefaultValue {
			get {
				return _DefaultValue;
			}
		}

		/// <summary>
		/// Gets or sets the name of the category that the Option will appear in within a PropertyGrid.
		/// </summary>
		public string Category {
			get {
				return _Category;
			}
		}

		/// <summary>
		/// Gets or sets whether or not the Option is hidden from the user.
		/// </summary>
		public bool Hidden {
			get {
				return _Hidden;
			}
		}

		/// <summary>
		/// Gets or sets whether or n ot the Option is read only.
		/// </summary>
		public bool ReadOnly {
			get {
				return _ReadOnly;
			}
		}

		/// <summary>
		/// Gets the type of the UI editor used to edit values of the Option.
		/// </summary>
		public Type Editor {
			get {
				return _Editor;
			}
		}

		/// <summary>
		/// Gets the type of the converter that will be used to convert the Option's values to and from strings.
		/// </summary>
		public Type Converter {
			get {
				return _Converter;
			}
		}

		/// <summary>
		/// Gets the OptionSet that this Option is a part of.
		/// </summary>
		public OptionSet Parent {
			get {
				return _Parent;
			}
			internal set {
				_Parent = value;
			}
		}

		/// <summary>
		/// Fired whenever one of the option's permanent values changes.
		/// </summary>
		public event OptionValueChangedEventHandler ValueChanged;

		/// <summary>
		/// Fires the ValueChanged event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnValueChanged(OptionValueChangedEventArgs e) {
			if (ValueChanged != null) {
				ValueChanged(this, e);
			}
		}

		/// <summary>
		/// Fired whenever one of the option's permanent values is about to be changed.
		/// </summary>
		public event OptionValueChangingEventHandler ValueChanging;

		/// <summary>
		/// Fires the ValueChanging event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnValueChanging(OptionValueChangingEventArgs e) {
			if (ValueChanging != null) {
				ValueChanging(this, e);
			}
		}

		/// <summary>
		/// Fired whenever one of the option's temporary values changes.
		/// </summary>
		internal event OptionValueChangedEventHandler TemporaryValueChanged;

		/// <summary>
		/// Fires the TemporaryValueChanged event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnTemporaryValueChanged(OptionValueChangedEventArgs e) {
			if (TemporaryValueChanged != null) {
				TemporaryValueChanged(this, e);
			}
		}

		/// <summary>
		/// Fired whenever one of the option's temporary values is about to be changed.
		/// </summary>
		internal event OptionValueChangingEventHandler TemporaryValueChanging;

		/// <summary>
		/// Fires the TemporaryValueChanging event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		protected void OnTemporaryValueChanging(OptionValueChangingEventArgs e) {
			if (TemporaryValueChanging != null) {
				TemporaryValueChanging(this, e);
			}
		}

		/// <summary>
		/// Checks whether or not the option has a value at the given index.
		/// </summary>
		/// <param name="ValueIndex">The index to check whether the option has a value at.</param>
		/// <returns>True if the option has a value at the given index, false if it does not.</returns>
		public bool HasValueAtIndex(int ValueIndex) {
			return _PermanentValues.ContainsKey(ValueIndex);
		}

		/// <summary>
		/// Applies the current temporary value to the permanent value.  Fires the ValueChanged event if there was a change.
		/// </summary>
		public void ApplyNewValue() {
			ApplyNewValue(_LowestValueIndex);
		}

		/// <summary>
		/// Applies the current temporary value at the given index to the permanent value.  Fires the ValueChanged event if there was a change.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to apply.</param>
		public void ApplyNewValue(int ValueIndex) {
			ApplyNewValue(ValueIndex, true);
		}

		/// <summary>
		/// Applies the current temporary value at the given index to the permanent value.  Fires the ValueChanged event if there was a change.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to apply.</param>
		/// <param name="CheckForChange">Whether or not to check if the value changed before applying the new value.</param>
		public void ApplyNewValue(int ValueIndex, bool CheckForChange) {
			// make sure we have a value at this index
			if (!HasValueAtIndex(ValueIndex)) {
				return;
			}

			// check if the value changed
			if (CheckForChange && !HasValueChanged(ValueIndex)) {
				return;
			}

			// fire a value changing event to see if we can store the new value
			OptionValueChangingEventArgs Args = new OptionValueChangingEventArgs(ValueIndex, _PermanentValues[ValueIndex], _TemporaryValues[ValueIndex]);
			OnValueChanging(Args);
			if (!Args.Cancel) {
				_PermanentValues[ValueIndex] = _TemporaryValues[ValueIndex];
				OnValueChanged(Args);
			}
		}

		/// <summary>
		/// Stores the current temporary values over the current permanent values.  Fires the ValueChanged event for any values that changed.
		/// </summary>
		public void ApplyNewValues() {
			ApplyNewValues(true);
		}

		/// <summary>
		/// Stores the current temporary values over the current permanent values.  Fires the ValueChanged event for any values that changed.
		/// </summary>
		/// <param name="CheckForChange">Whether or not to check if the value changed before applying the new value.</param>
		public void ApplyNewValues(bool CheckForChange) {
			foreach (int ValueIndex in _TemporaryValues.Keys) {
				ApplyNewValue(ValueIndex, CheckForChange);
			}
		}

		/// <summary>
		/// Stores the permanent value over the current temporary value.
		/// </summary>
		public void CancelNewValue() {
			CancelNewValue(_LowestValueIndex);
		}

		/// <summary>
		/// Stores the permanent value at the given index over the current temporary value.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to cancel.</param>
		public void CancelNewValue(int ValueIndex) {
			CancelNewValue(ValueIndex, true);
		}

		/// <summary>
		/// Stores the permanent value of the given type over the current temporary value.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to cancel.</param>
		/// <param name="CheckForChange">Whether or not to check if the value was changed before overwriting the old value.</param>
		public void CancelNewValue(int ValueIndex, bool CheckForChange) {
			// make sure we have a value at this index
			if (!HasValueAtIndex(ValueIndex)) {
				return;
			}

			// check if the value changed
			if (CheckForChange && !HasValueChanged(ValueIndex)) {
				return;
			}

			// the value changed, store the old value and fire the event
			OptionValueChangedEventArgs Args = new OptionValueChangedEventArgs(ValueIndex, _TemporaryValues[ValueIndex], _PermanentValues[ValueIndex]);
			_TemporaryValues[ValueIndex] = _PermanentValues[ValueIndex];
			OnTemporaryValueChanged(Args);
		}

		/// <summary>
		/// Stores the current permanent values over the current temporary values.
		/// </summary>
		public void CancelNewValues() {
			CancelNewValues(true);
		}

		/// <summary>
		/// Stores the current permanent values over the current temporary values.
		/// </summary>
		/// <param name="CheckForChange">Whether or not to check if the value was changed before overwriting the old value.</param>
		public void CancelNewValues(bool CheckForChange) {
			foreach (int ValueIndex in _PermanentValues.Keys) {
				CancelNewValue(ValueIndex, CheckForChange);
			}
		}

		/// <summary>
		/// Checks whether or not the value at the given index has changed.
		/// </summary>
		/// <returns>True if the permanent and temporary values at the given index differ, false if they do not.</returns>
		public bool HasValueChanged() {
			return HasValueChanged(_LowestValueIndex);
		}

		/// <summary>
		/// Checks whether or not the value at the given index has changed.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to check.</param>
		/// <returns>True if the permanent and temporary values at the given index differ, false if they do not or the option has no values at the index.</returns>
		public bool HasValueChanged(int ValueIndex) {
			// make sure we have values at this index first
			if (!HasValueAtIndex(ValueIndex)) {
				return false;
			}

			// must call Equals rather than using !=
			// because Equals is virtual and will be called correctly on value types
			// whereas the != operator will always compare by reference since our values are always of type object

			// since we're calling Equals, we need to make sure the object isn't null first
			if (_TemporaryValues[ValueIndex] == null) {
				return (_PermanentValues[ValueIndex] == null);
			} else {
				return (!_TemporaryValues[ValueIndex].Equals(_PermanentValues[ValueIndex]));
			}
		}

		/// <summary>
		/// Creates a set of Attributes which reflect the current setup of the Option.
		/// </summary>
		/// <returns>An array of Attributes which reflect the current setup of the Option.</returns>
		public Attribute[] CreateAttributes() {
			// create an attribute list
			List<Attribute> Attributes = new List<Attribute>(7);

			// add any necessary attributes to the list
			if (Description != null) {
				Attributes.Add(new DescriptionAttribute(Description));
			}
			if (DefaultValue != null) {
				Attributes.Add(new DefaultValueAttribute(DefaultValue));
			}
			if (Category != null) {
				Attributes.Add(new CategoryAttribute(Category));
			}
			if (Hidden || !HasValueAtIndex(OptionDescriptor.CurrentValueIndex)) {
				Attributes.Add(new BrowsableAttribute(false));
			}
			if (ReadOnly) {
				Attributes.Add(new ReadOnlyAttribute(true));
			}
			if (Editor != null) {
				Attributes.Add(new EditorAttribute(Editor, typeof(UITypeEditor)));
			}
			if (Converter != null) {
				Attributes.Add(new TypeConverterAttribute(Converter));
			}

			// turn the list into an array to return
			return Attributes.ToArray();
		}

		/// <summary>
		/// Saves the option's name and value to an XML tag.
		/// </summary>
		/// <returns>An XML tag containing the option's name and value.</returns>
		public XmlTag Save() {
			return Save(_LowestValueIndex, true);
		}

		/// <summary>
		/// Saves the option's name and value at the given index to an XML tag.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to save.</param>
		/// <returns>An XML tag containing the option's name and value, or null if the option has no value at that index.</returns>
		public XmlTag Save(int ValueIndex) {
			return Save(ValueIndex, true);
		}

		/// <summary>
		/// Saves the option's name and value to an XML tag.
		/// </summary>
		/// <param name="IgnoreDuplicateValues">If true then null will be returned if the value at the given index is the same as the value at the next higher index.  See Remarks of the int,bool overload.</param>
		/// <returns>An XML tag containing the option's name and value.</returns>
		public XmlTag Save(bool IgnoreDuplicateValues) {
			return Save(_LowestValueIndex, IgnoreDuplicateValues);
		}

		/// <summary>
		/// Saves the option's name and value at the given index to an XML tag.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to save.</param>
		/// <param name="IgnoreDuplicateValues">If true then null will be returned if the value at the given index is the same as the value at the next higher index.  See Remarks.</param>
		/// <returns>An XML tag containing the option's name and value, or null if the option has no value at that index.</returns>
		/// <remarks>
		/// IgnoreDuplicateValues is useful for saving space because it won't save duplicate values.
		/// If the value at the next higher index is the same as the value at this index, then the value at this index can be safely ignored.
		/// This is because when the options are initialized again, the value at this index will initialize to the value at the next higher index if it doesn't have its own value yet.
		/// For this to work, higher value indices should always be loaded before lower ones.
		/// </remarks>
		public XmlTag Save(int ValueIndex, bool IgnoreDuplicateValues) {
			// check if we have a value at this index
			if (!HasValueAtIndex(ValueIndex)) {
				return null;
			}

			// check if we're ignoring duplicates
			if (IgnoreDuplicateValues) {
				// find the next higher value index
				List<int> Indices = new List<int>(_PermanentValues.Keys);
				Indices.Sort();

				// work our way from the end until we find the index we're saving
				object PreviousValue = _DefaultValue;
				int i = Indices.Count - 1;
				while (Indices[i] != ValueIndex) {
					PreviousValue = _PermanentValues[Indices[i]];
					--i;
				}

				// PreviousValue now holds the value of the next higher index
				if (_PermanentValues[ValueIndex] == null) {
					// first, check if both values are null
					if (PreviousValue == null) {
						return null;
					}
				} else if (_PermanentValues[ValueIndex].Equals(PreviousValue)) {
					// if the previous value is the same as the value at this index, we don't have to save this value
					// see HasValueChanged for an explanation of using .Equals() instead of ==
					return null;
				}
			}

			// create a dictionary containing our name and value
			Dictionary<string, string> Attributes = new Dictionary<string,string>(2);
			Attributes.Add(XmlNameAttribute, Name);
			Attributes.Add(XmlValueAttribute, EncryptValue(ValueAsString(ValueIndex), _EncryptKey));

			// return an XML tag with the attributes
			return new XmlTag(XmlTagName, Attributes);
		}

		/// <summary>
		/// Loads the option's value from the given XML tag.
		/// </summary>
		/// <param name="Tag">The tag to load one of the option's values from.</param>
		public void Load(XmlTag Tag) {
			Load(Tag, _LowestValueIndex);
		}

		/// <summary>
		/// Loads one of the option's values from the given XML tag.
		/// </summary>
		/// <param name="Tag">The tag to load one of the option's values from.</param>
		/// <param name="ValueIndex">The value index to load the value into.</param>
		public void Load(XmlTag Tag, int ValueIndex) {
			// make sure we have an option tag
			if (Tag.Name != XmlTagName) {
				throw new ArgumentException("Given XML tag's name is '" + Tag.Name + "' where '" + XmlTagName + "' was expected.");
			}

			// check if we're already initialized or not
			if (Initialized) {
				// check to make sure our name in the tag is the name we've already got
				if (Tag.Attributes[XmlNameAttribute] != _Name) {
					throw new ArgumentException("The given XML tag contains a value for an Option named '" + Tag.Attributes[XmlNameAttribute] + "' but this Option's name is '" + _Name + "'.");
				}

				// since we're already initialized, we already know what our name and value type are
				// so we need to convert the value read in to our value type
				this[ValueIndex] = CreateConverter().ConvertFrom(DecryptValue(Tag.Attributes[XmlValueAttribute], _EncryptKey));
			} else {
				// we're not initialized yet, so we need to know our name from the tag
				_Name = Tag.Attributes[XmlNameAttribute];

				// since we're not initialized we don't know our value type
				// our value in the file is a string, so we'll use that until we're initialized
				_ValueType = typeof(string);
				
				// set the value and apply the changes
				this[ValueIndex] = Tag.Attributes[XmlValueAttribute];
			}

			// apply the value we read in to our permanent values
			ApplyNewValue(ValueIndex);
		}

		/// <summary>
		/// Clones the Option.  The Option's current values are cloned if they are value types or implement ICloneable.  Otherwise the returned Option's values are copied by reference.
		/// </summary>
		/// <returns>A deep copy of the Option (with the possible exception of its values, see Summary).</returns>
		/// <remarks>
		/// The Option's current values are cloned if they are value types or implement ICloneable, otherwise they are copied by reference.
		/// The Option's ValueType, Converter, and Editor are copied by reference, but since the Type objects themselves aren't going to change, this should be fine.
		/// The Option's parent OptionSet is not copied to the clone's.  The clone's parent set is set to null.
		/// </remarks>
		public object Clone() {
			Option O            = new Option(this._Name);
			O._Category         = this._Category;
			O._Converter        = this._Converter;
			O._Description      = this._Description;
			O._Editor           = this._Editor;
			O._EncryptKey       = this._EncryptKey;
			O._Hidden           = this._Hidden;
			O._Initialized      = this._Initialized;
			O._LowestValueIndex = this._LowestValueIndex;
			O._Name             = this._Name;
			O._ReadOnly         = this._ReadOnly;
			O._ValueType        = this._ValueType;
			
			if (_DefaultValue is ICloneable) {
				// default value is cloneable, clone it
				O._DefaultValue = (this._DefaultValue as ICloneable).Clone();
			} else {
				// if default value is a value type, it will be cloned implicitly, otherwise it'll be copied by reference
				O._DefaultValue = this._DefaultValue;
			}

			// run through each permanent value and clone it in a similar manner to the default value was
			O._PermanentValues = new Dictionary<int,object>(this._PermanentValues.Count);
			foreach (KeyValuePair<int,object> Pair in this._PermanentValues) {
				object Clone;
				if (Pair.Value is ICloneable) {
					Clone = (Pair.Value as ICloneable).Clone();
				} else {
					Clone = Pair.Value;
				}
				O[Pair.Key] = Clone;
			}
			O.ApplyNewValues(false);

			// copy the event handlers (except for the parent's)
			if (this.ValueChanged != null) {
				foreach (Delegate D in this.ValueChanged.GetInvocationList()) {
					O.ValueChanged += D as OptionValueChangedEventHandler;
				}
				if (this.Parent != null) {
					O.ValueChanged -= this.Parent.OptionValueChangedReceived;
				}
			}
			if (this.ValueChanging != null) {
				foreach (Delegate D in this.ValueChanging.GetInvocationList()) {
					O.ValueChanging += D as OptionValueChangingEventHandler;
				}
				if (this.Parent != null) {
					O.ValueChanging -= this.Parent.OptionValueChangingReceived;
				}
			}
			if (this.TemporaryValueChanged != null) {
				foreach (Delegate D in this.TemporaryValueChanged.GetInvocationList()) {
					O.TemporaryValueChanged += D as OptionValueChangedEventHandler;
				}
				if (this.Parent != null) {
					O.TemporaryValueChanged -= this.Parent.OptionTemporaryValueChangedReceived;
				}
			}
			if (this.TemporaryValueChanging != null) {
				foreach (Delegate D in this.TemporaryValueChanging.GetInvocationList()) {
					O.TemporaryValueChanging += D as OptionValueChangingEventHandler;
				}
				if (this.Parent != null) {
					O.TemporaryValueChanging -= this.Parent.OptionTemporaryValueChangingReceived;
				}
			}

			// return the clone
			return O;
		}

		/// <summary>
		/// Gets the current temporary value of the option.
		/// </summary>
		/// <returns>The Option's current temporary value at the given index.</returns>
		internal object GetTemporaryValue() {
			return _TemporaryValues[_LowestValueIndex];
		}

		/// <summary>
		/// Gets the current temporary value at the given index from the option.
		/// </summary>
		/// <param name="ValueIndex">The index to retrieve the value from.</param>
		/// <returns>The Option's current temporary value at the given index, or null if the option doesn't have a value at that index.</returns>
		internal object GetTemporaryValue(int ValueIndex) {
			if (_TemporaryValues.ContainsKey(ValueIndex)) {
				return _TemporaryValues[ValueIndex];
			} else {
				return null;
			}
		}

		/// <summary>
		/// Converts the current value at the given index to a string.
		/// </summary>
		/// <param name="ValueIndex">The index of the value to convert.</param>
		/// <returns>A string representation of the current value at the given index.</returns>
		private string ValueAsString(int ValueIndex) {
			return CreateConverter().ConvertTo(this[ValueIndex], typeof(string)) as string;
		}

		/// <summary>
		/// Creates a converter that can be used with the option.
		/// </summary>
		/// <returns>A type converter to use with the option.</returns>
		private TypeConverter CreateConverter() {
			if (Converter != null) {
				// if we have a custom converter, create an instance and use that
				return Converter.GetConstructor(System.Type.EmptyTypes).Invoke(null) as TypeConverter;
			} else {
				// otherwise get an appropriate one from the system
				return TypeDescriptor.GetConverter(ValueType);
			}
		}
	}

	/// <summary>
	/// A struct containing all of the initialization information for an Option.
	/// </summary>
	public class OptionInitializer {
		// member data
		private object _DefaultValue = null;  // the Option's default value
		private Type   _ValueType    = null;  // the type of the Option's values
		private string _Description  = null;  // the Option's description
		private string _Category     = null;  // the name of the Option's category
		private bool   _Hidden       = false; // whether or not the Option is hidden from the user
		private bool   _ReadOnly     = false; // whether or not the Option is read only
		private Type   _Editor       = null;  // the UI editor used to edit the Option's values
		private Type   _Converter    = null;  // the converter used to convert the Option's values to and from strings
		private string _EncryptKey   = null;  // the key to use to encrypt and decrypt the Option's value
		private int[]  _ValueIndices = null;  // the set of value indices that the Option uses to store values at

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		public OptionInitializer(Type ValueType, object DefaultValue)
			: this(ValueType, DefaultValue, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, params int[] ValueIndices)
			: this(ValueType, DefaultValue, null, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description)
			: this(ValueType, DefaultValue, Description, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, null, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category)
			: this(ValueType, DefaultValue, Description, Category, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, Category, false, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden)
			: this(ValueType, DefaultValue, Description, Category, Hidden, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, Category, Hidden, false, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, null, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, null, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, Editor, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor, params int[] ValueIndices)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, Editor, null, ValueIndices) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		/// <param name="Converter">The TypeConverter-derived type to use to convert the option's values to and from strings.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor, Type Converter)
			: this(ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, Editor, Converter, 0) {
		}

		/// <summary>
		/// Creates a new OptionInitializer.
		/// </summary>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		/// <param name="Converter">The TypeConverter-derived type to use to convert the option's values to and from strings.</param>
		/// <param name="ValueIndices">The indices that the option will store values at.</param>
		public OptionInitializer(Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor, Type Converter, params int[] ValueIndices) {
			_ValueType    = ValueType;
			_DefaultValue = DefaultValue;
			_Description  = Description;
			_Category     = Category;
			_Hidden       = Hidden;
			_ReadOnly     = ReadOnly;
			_Editor       = Editor;
			_Converter    = Converter;
			_EncryptKey   = EncryptKey;
			_ValueIndices = ValueIndices;
		}

		/// <summary>
		/// Gets or sets the type of the option's values.
		/// </summary>
		public Type ValueType {
			get {
				return _ValueType;
			}
			set {
				_ValueType = value;
			}
		}

		/// <summary>
		/// Gets or sets the option's description.
		/// </summary>
		public string Description {
			get {
				return _Description;
			}
			set {
				_Description = value;
			}
		}

		/// <summary>
		/// Gets or sets the option's default value.
		/// </summary>
		public object DefaultValue {
			get {
				return _DefaultValue;
			}
			set {
				_DefaultValue = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the category that the option will appear in.
		/// </summary>
		public string Category {
			get {
				return _Category;
			}
			set {
				_Category = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not the option is hidden from the user.
		/// </summary>
		public bool Hidden {
			get {
				return _Hidden;
			}
			set {
				_Hidden = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not the option is read only.
		/// </summary>
		public bool ReadOnly {
			get {
				return _ReadOnly;
			}
			set {
				_ReadOnly = value;
			}
		}

		/// <summary>
		/// Gets or sets the type of the UI editor used to edit values of the option.
		/// </summary>
		public Type Editor {
			get {
				return _Editor;
			}
			set {
				_Editor = value;
			}
		}

		/// <summary>
		/// Gets or sets type of the converter that will be used to convert the option's values to and from strings.  The type must provide a default constructor.
		/// </summary>
		public Type Converter {
			get {
				return _Converter;
			}
			set {
				_Converter = value;
			}
		}

		/// <summary>
		/// Gets the key to use to encrypt and decrypt the option's value.
		/// </summary>
		public string EncryptKey {
			get {
				return _EncryptKey;
			}
			set {
				_EncryptKey = value;
			}
		}

		/// <summary>
		/// Gets the indices that the option will store values at.
		/// </summary>
		public int[] ValueIndices {
			get {
				return _ValueIndices;
			}
			set {
				_ValueIndices = value;
			}
		}
	}

	/// <summary>
	/// The prototype of the ValueChanged event of Option.
	/// </summary>
	/// <param name="Sender">The Option whose value changed.</param>
	/// <param name="e">Arguments about the value that changed.</param>
	public delegate void OptionValueChangedEventHandler(object Sender, OptionValueChangedEventArgs e);

	/// <summary>
	/// The arguments passed to an Option's ValueChanged event.
	/// </summary>
	public class OptionValueChangedEventArgs : EventArgs {
		// member data
		private int    _ValueIndex; // the index of value that was changed
		private object _OldValue;   // the old value of the option
		private object _NewValue;   // the new value of the option

		/// <summary>
		/// Creates a new ValueChangedEventArgs.
		/// </summary>
		/// <param name="ValueIndex">The index of the value that was changed.</param>
		/// <param name="OldValue">The old value of the option.</param>
		/// <param name="NewValue">The new value of the option.</param>
		public OptionValueChangedEventArgs(int ValueIndex, object OldValue, object NewValue) {
			_ValueIndex = ValueIndex;
			_OldValue = OldValue;
			_NewValue = NewValue;
		}

		/// <summary>
		/// Gets the index of the value that was changed.
		/// </summary>
		public int ValueIndex {
			get {
				return _ValueIndex;
			}
		}

		/// <summary>
		/// Gets the old value of the option.
		/// </summary>
		public object OldValue {
			get {
				return _OldValue;
			}
		}

		/// <summary>
		/// Gets the new value of the option.
		/// </summary>
		public object NewValue {
			get {
				return _NewValue;
			}
		}
	}
	
	/// <summary>
	/// The prototype of the ValueChanging event of Option.
	/// </summary>
	/// <param name="Sender">The Option whose value changed.</param>
	/// <param name="e">Arguments about the value that changed.</param>
	public delegate void OptionValueChangingEventHandler(object Sender, OptionValueChangingEventArgs e);

	/// <summary>
	/// The arguments passed to an Option's ValueChanging event.
	/// </summary>
	public class OptionValueChangingEventArgs : OptionValueChangedEventArgs {
		// member data
		private bool _Cancel; // whether or not to cancel the event

		/// <summary>
		/// Creates a new ValueChangingEventArgs.
		/// </summary>
		/// <param name="ValueIndex">The index of the value that was changed.</param>
		/// <param name="OldValue">The old value of the option.</param>
		/// <param name="NewValue">The new value of the option.</param>
		public OptionValueChangingEventArgs(int ValueIndex, object OldValue, object NewValue)
			: base(ValueIndex, OldValue, NewValue) {
			_Cancel = false;
		}

		/// <summary>
		/// Gets or sets whether or not the event should be cancelled.
		/// </summary>
		public bool Cancel {
			get {
				return _Cancel;
			}
			set {
				_Cancel = value;
			}
		}
	}
}
